// matrix.h
#ifndef MATRIX_H
#define MATRIX_H

#include <ostream>
#include "matrix_exception.h"
#include "matrix_element_trait.h"
#include "matrix_implementation.h"

/**
 * Matrix class
 * Template argument T is the type of the elements in the Matrix. It must have the following opperations:
 *   T(int) // T(1) must initialize to an 'identity' value
 *   T() // Must initialize to a 'zero' value the given type
 *   bool operator> (T) const
 *   T & operator+= (T)
 *   T & operator/= (T)
 *   T & operator*= (U) // Must exist for all scalar types U which are allowed to be multiplied by a matrix (double and T should do)
 *   T const operator* (T) const
 *   T & operator-= (T)
 *   T const operator- (T) const
 *   T const operator- () const
 *
 * External operations:
 *   std::ostream & operator<< (std::ostream &, T) 
 * 
 * Template argument ElementTrait specifies traits for the elements of the matrix. It should contain the members
 * defined in MatrixElementTrait. All calls regarding the elements of the matrix are simply forwarded to the 
 * MatrixImplementation object which is pointed to by _value. The MatrixImplementation belonging to a matrix
 * is stored in a pointer. This saves some memory compared to using shared_ptr, but makes it a bit more complex,
 * because I have to handle reference counting of the implementation object by myself.
 *
 * A little perculiar note:
 * Running valgrind on my test suite on the current implementation I get 200 allocs.
 * If I remove 'const' from the return type of operator*, operator+, and Invert (here and MatrixImplementation) 
 * I only get 170. This is because the move ctor taking a MatrixImplementation does not take a const argument.
 * However, valgrind is run with optmization -O0, which means that return value optimization is (probably)
 * not enabled, and thus the current implementation would not need to use any of the copy/move ctors.
 */
template<typename T, typename ElementTrait = MatrixElementTrait<T> >
class Matrix
{
private:
    // Pointer to the implementation object
    MatrixImplementation<T,ElementTrait> * _value;

    // Private ctor which eager copies the MatrixImplementation argument
    Matrix(MatrixImplementation<T,ElementTrait> const & toCopy);
    Matrix(MatrixImplementation<T,ElementTrait> && toMove);

    // Check for uniqueness of the implementation pointed to and copies if necessary
    void copyIfNecessary();

public:
    // Construct a matrix of specified dimension. Avoid implcit conversion from int
    explicit Matrix(size_t dim);
    // Lazy copy (point to same implementation)
    Matrix(Matrix<T,ElementTrait> const & toCopy);
    // Move implementation to new Matrix obj
    Matrix(Matrix<T,ElementTrait> && toMove);
    // Dtor, decrement ref counter on the MatroixImplementation
    ~Matrix();

    // Check for identity
    bool IsIdentity() const;
    // Swap the elements of two matrices
    void Swap(Matrix<T,ElementTrait> & other);
    // Return inverted matrix without altering current
    Matrix<T,ElementTrait> const Invert() const throw (MatrixException);
    // Get the dimensions of the matrix
    size_t const Dimension() const;
    // Used for testing purposes (check if copy-on-write works)
    T const * GetInternal() const;

    // Opperators

    // Element access operators
    T & operator() (size_t row, size_t col) throw (MatrixException);
    T const operator() (size_t row, size_t col) const throw (MatrixException);
    // Arithmetic operators
    Matrix<T,ElementTrait> & operator+= (Matrix<T,ElementTrait> const & other) throw (MatrixException);
    Matrix<T,ElementTrait> const operator+ (Matrix<T,ElementTrait> const & other) const throw (MatrixException);
    Matrix<T,ElementTrait> & operator*= (Matrix<T,ElementTrait> const & other) throw (MatrixException);
    Matrix<T,ElementTrait> const operator* (Matrix<T,ElementTrait> const & other) const throw (MatrixException);
    // Equality operators (mainly for testing, but is probably useful to the user of the library)
	bool operator== (Matrix<T,ElementTrait> const & other) const;  
	bool operator!= (Matrix<T,ElementTrait> const & other) const;
    // Assign new matrix to current (lazy copy by adding reference)
    Matrix<T,ElementTrait> & operator= (Matrix<T,ElementTrait> const & other);

    // Arithmetic operators with scalars
    template<typename U>
    Matrix<T,ElementTrait> & operator*= (U const & other);
    template<typename U>
    Matrix<T,ElementTrait> const operator* (U const & other) const;
    
    // Create identity matrix of given dimension
    static Matrix<T,ElementTrait> Identity(size_t dim);
    // Generate matrix of specified dimension with random elements 
    static Matrix<T,ElementTrait> RandomMatrix(size_t dim);
};

// Print Matrix to stream (mainly for debugging)
template<typename T, typename ElementTrait >
std::ostream & operator<< (std::ostream & stream, Matrix<T,ElementTrait> const & matrix);
// Enables scalar * matrix (as opposed to matrix * scalar which is defined above)
template<typename T, typename U, typename ElementTrait >
Matrix<T,ElementTrait> const operator* (U const & element, Matrix<T,ElementTrait> const & matrix);

#include "matrix_definition.h"

#endif // MATRIX_H
